/*
 * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../Clipboard.h"

#include <gtk/gtk.h>

namespace Titanium {

static void GetClipboardData(GtkClipboard* clipboard, GtkSelectionData* selectionData,
    guint info, gpointer data);
static void ClearClipboardData(GtkClipboard* clipboard, gpointer data);
static void OwnerChangeCallback(GtkClipboard*, GdkEvent*, gpointer);
static GdkAtom uriListAtom = gdk_atom_intern("text/uri-list", FALSE);

static inline GtkClipboard* GetClipboard()
{
    static GtkClipboard* clipboard = NULL;

    if (!clipboard)
    {
        clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
        g_signal_connect(
            G_OBJECT(clipboard), "owner-change",
            G_CALLBACK(OwnerChangeCallback), NULL);
    }
    return clipboard;
}

struct ClipboardPrivate
{
    gchar* text;
    gchar** uris;
    bool preserve;
    bool ownClipboard;

    enum ClipboardDataIds { TEXT_DATA, URI_LIST_DATA, IMAGE_DATA };
    inline void SetText(std::string& newText)
    {
        this->ClearText();
        this->text = g_strdup(newText.c_str());
    }

    inline void SetURIList(std::vector<std::string>& newURIList)
    {
        int size = newURIList.size() + 1;
        this->ClearURIList();
        this->uris = g_new0(gchar*, size);

        for (int i = 0; i < size - 1; i++)
            this->uris[i] = g_strdup(newURIList[i].c_str());
    }

    inline void ClearText()
    {
        g_free(this->text);
        this->text = NULL;
    }

    inline void ClearURIList()
    {
        g_strfreev(this->uris);
        this->uris = NULL;
    }

    inline void ClearImage()
    {
        // TODO: Clear image data here.
    }

    void SetupClipboardContentTypes()
    {
        GtkTargetList* list = gtk_target_list_new(NULL, 0);

        // TODO: Support for images
        if (!this->text && !this->uris)
            return;

        if (this->text)
            gtk_target_list_add_text_targets(list, ClipboardPrivate::TEXT_DATA);

        if (this->uris)
            gtk_target_list_add_uri_targets(list, ClipboardPrivate::URI_LIST_DATA);

        int size = 0;
        GtkTargetEntry* table = gtk_target_table_new_from_list(list, &size);
        if (table)
        {
            // gtk_clipboard_set_with_data may try to clear our clipboard when we
            // call it, so we turn on a flag here which prevents our clipboard data
            // from being freed during this call.
            this->preserve = true;
            GtkClipboard* clipboard = GetClipboard();
            if (gtk_clipboard_set_with_data(clipboard, table, size, GetClipboardData,
                ClearClipboardData, NULL))
            {
                this->ownClipboard = true;
                gtk_clipboard_set_can_store(clipboard, NULL, 0);
            }
            this->preserve = false;

            gtk_target_table_free(table, size);
        }

        gtk_target_list_unref(list);
    }

} priv;

static void GetClipboardData(GtkClipboard* clipboard, GtkSelectionData* selectionData,
    guint info, gpointer data)
{
    if (info == ClipboardPrivate::TEXT_DATA && priv.text)
    {
        gtk_selection_data_set_text(selectionData, priv.text, -1);
    }

    else if (info == ClipboardPrivate::URI_LIST_DATA && priv.uris)
        gtk_selection_data_set_uris(selectionData, priv.uris);

    // TODO: Image data
}

static void ClearClipboardData(GtkClipboard* clipboard, gpointer data)
{
    if (priv.preserve)
        return;

    priv.ClearText();
    priv.ClearURIList();
    priv.ClearImage();
}

static void OwnerChangeCallback(GtkClipboard*, GdkEvent*, gpointer)
{
    priv.ownClipboard = false;
}

Clipboard::~Clipboard()
{
}

void Clipboard::SetTextImpl(std::string& newText)
{
    priv.SetText(newText);
    priv.SetupClipboardContentTypes();
}

std::string& Clipboard::GetTextImpl()
{
    static std::string clipboardText;

    if (this->HasText())
    {
        gchar* clipboardChars = gtk_clipboard_wait_for_text(GetClipboard());
        if (!clipboardChars)
        {
            clipboardText = "";
            throw ValueException::FromString("Could not retrieve clipboard data");
        }

        clipboardText = clipboardChars;
        g_free(clipboardChars);
    }
    else
    {
        clipboardText = "";
    }

    return clipboardText;
}

bool Clipboard::HasTextImpl()
{
    return (priv.ownClipboard && priv.text) ||
        (!priv.ownClipboard && gtk_clipboard_wait_is_text_available(GetClipboard()));
}

void Clipboard::ClearTextImpl()
{
    priv.ClearText();
}

BytesRef Clipboard::GetImageImpl(std::string& mimeType)
{
    BytesRef image(0);
    return image;
}

void Clipboard::SetImageImpl(std::string& mimeType, BytesRef image)
{
}

bool Clipboard::HasImageImpl()
{
    return false;
}

void Clipboard::ClearImageImpl()
{
}

std::vector<std::string>& Clipboard::GetURIListImpl()
{
    static std::vector<std::string> uriList;
    uriList.clear();

    GtkSelectionData* data = gtk_clipboard_wait_for_contents(GetClipboard(), uriListAtom);
    if (!data)
        return uriList;

    gchar** uris = gtk_selection_data_get_uris(data);
    if (uris)
    {
        gchar** current = uris;
        while (*current)
            uriList.push_back(*current++);
        g_strfreev(uris);
    }
    gtk_selection_data_free(data);
    return uriList;
}

void Clipboard::SetURIListImpl(std::vector<std::string>& uriList)
{
    priv.SetURIList(uriList);
    priv.SetupClipboardContentTypes();
}

bool Clipboard::HasURIListImpl()
{
    if (priv.ownClipboard)
        return priv.uris;
    else
        return gtk_clipboard_wait_is_target_available(GetClipboard(), uriListAtom);
}

void Clipboard::ClearURIListImpl()
{
    priv.ClearURIList();
    priv.SetupClipboardContentTypes();
}

} // namespace Titanium

